package com.net.io.bio;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * BIO(Blocking IO)
 *      同步阻塞模型，一个客户端连接对应一个处理线程
 * 缺点：
 *      1、IO代码里read操作是阻塞操作，如果连接不做数据读写操作会导致线程阻塞，浪费资源
 *      2、如果线程很多，会导致服务器线程太多，压力太大。
 * 应用场景：
 *      BIO 方式适用于连接数目比较小且固定的架构， 这种方式对服务器资源要求比较高， 但程序简单易理解。
 * 
 * @author zhangtengfei
 * @date 2021/7/29 14:59
 */
public class BioSocketServer {
    /**
     * 服务端示例
     */
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9000);
        /**
         * 采用 BIO 通信模型 的服务端，通常由一个独立的 Acceptor 线程负责监听客户端的连接。
         * 我们一般通过在 while(true) 循环中服务端会调用 accept() 方法等待接收客户端的连接的方式监听请求，
         * 请求一旦接收到一个连接请求，就可以建立通信套接字在这个通信套接字上进行读写操作，
         * 此时不能再接收其他客户端连接请求，只能等待同当前连接的客户端的操作执行完成， 
         * 不过可以通过多线程来支持多个客户端的连接
         */
        while (true) {
            System.out.println("等待连接。。");
            //阻塞方法
            Socket socket = serverSocket.accept(); 
            System.out.println("有客户端连接了。。");
            /**
             * 同步阻塞模型，一个客户端连接对应一个处理线程
             * 如果线程很多，会导致服务器线程太多，压力太大。
             */
            new Thread(() -> {
                try {
                    handler(socket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

    /**
     * 如果要让 BIO 通信模型 能够同时处理多个客户端请求，就必须使用多线程（主要原因是 socket.accept()、 socket.read()、 socket.write() 涉及的三个主要函数都是同步阻塞的），
     * 也就是说它在接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理，处理完成之后，通过输出流返回应答给客户端，线程销毁。
     * 这就是典型的 一请求一应答通信模型 。
     * 我们可以设想一下如果这个连接不做任何事情的话就会造成不必要的线程开销，不过可以通过 线程池机制 改善，线程池还可以让线程的创建和回收成本相对较低。
     * 使用FixedThreadPool 可以有效的控制了线程的最大数量，保证了系统有限的资源的控制，实现了N(客户端请求数量):M(处理客户端请求的线程数量)的伪异步I/O模型（N 可以远远大于 M）
     */
    private static void handler(Socket socket) throws IOException {
        System.out.println("thread id = " + Thread.currentThread().getId());
        byte[] bytes = new byte[1024];

        System.out.println("准备read。。");
        /**
         * 接收客户端的数据，阻塞方法，没有数据可读时就阻塞
         * IO代码里read操作是阻塞操作，如果连接不做数据读写操作会导致线程阻塞，浪费资源
         */
        int read = socket.getInputStream().read(bytes);
        System.out.println("read完毕。。");
        if (read != -1){
            System.out.println("接收到客户端的数据：" + new String(bytes, 0, read));
            System.out.println("thread id = " + Thread.currentThread().getId());

        }
        socket.getOutputStream().write("HelloClient".getBytes());
        socket.getOutputStream().flush();
    }
}